//
// Created by Administrator on 2020/9/17.
//
#include <iostream>
#include <chrono>
#include <thread>
#include "udp_client.h"
#include "stun_message.h"

using namespace std;
using namespace asio::ip;

const std::string chat_message::punch_header = "PUNCH-REQ";

UdpClient::UdpClient(const uint16_t& local_port, asio::io_context& io_context, PeerMgr &peer_mgr)
    : socket_(io_context, udp::endpoint(udp::v4(), local_port))
    , count_(0)
    , resolver_(io_context)
    , peer_mgr_(peer_mgr)
{
    memset(msg.data(), 0x00, 512);
    //do_receive();
}

//UdpClient::UdpClient(const string& address, const string& port)
//          :socket_(io_context_, udp::endpoint(udp::v4(), 55667))
//{
//    remote_endpoints_ = resolver_.resolve(udp::v4(), address, port);
//}

void UdpClient::start(){
    io_context_.run();
}

int UdpClient::send_data(const char* send_buff, size_t length){
    int send_length = socket_.send_to(asio::buffer(send_buff, length), *remote_endpoints_.begin());
    return send_length;
}

size_t UdpClient::read_data(){
    memset(msg.data(), 0x00,512);
    udp::endpoint sender_endpoint;
    size_t recv_len = socket_.receive_from(asio::buffer(msg.data(),512), sender_endpoint);

    std::unique_ptr<StunMessage> stun_message(new StunMessage());
    stun_message->stun_parse_message(msg.data(), recv_len);
    reflexive_port_ = stun_message->get_reflexive_port();
    reflexive_ip_ = stun_message->get_reflexive_address();
    std::cout << "reflexive address:port: "<< reflexive_ip_ << ":"<< dec << reflexive_port_<< std::endl;
    return recv_len;
}

int UdpClient::send_data(const char* send_buff, size_t length, const std::string& ip, const std::string& port){
    remote_endpoints_ = resolver_.resolve(udp::v4(), ip, port);
    int send_length = socket_.send_to(asio::buffer(send_buff, length), *remote_endpoints_.begin());
    return send_length;
}

int UdpClient::send_data(const char* send_buff, size_t length, const std::string& ip, const uint16_t & port){
    udp::endpoint  endpoint_(address_v4::from_string(ip), port);
    int send_length = socket_.send_to(asio::buffer(send_buff, length), endpoint_);
    return send_length;
}

int UdpClient::send_data(const char* send_buff, size_t length, PeerInfo &pi)
{
    std::set<uint16_t> ports = pi.ports();

    for (auto port : ports) {
        remote_endpoints_ = resolver_.resolve(udp::v4(), pi.ip(), std::to_string(port));
        int send_length = socket_.send_to(asio::buffer(send_buff, length), *remote_endpoints_.begin());
    }
    return 0;
}


int UdpClient::send_data(const char* send_buff, size_t length, PeerInfoPtr pi)
{
    std::set<uint16_t> ports = pi->ports();

    for (auto port : ports) {
        if (port && !pi->ok()) {
            remote_endpoints_ = resolver_.resolve(udp::v4(), pi->ip(), std::to_string(port));
            int send_length = socket_.send_to(asio::buffer(send_buff, length), *remote_endpoints_.begin());

            std::cout << "send msg: " << send_buff \
            << " to: " << pi->ip() \
            << " :" << port \
            << std::endl;
        }
    }
    return 0;
}


void UdpClient::async_send_data(const char* send_buff, size_t length, const std::string& ip, const uint16_t & port){
    std::cout << "send_data: " << ip << ":"<< port << std::endl;
    udp::endpoint  endpoint_(address_v4::from_string(ip), port);
    socket_.async_send_to(
            asio::buffer(send_buff, length), endpoint_,
            [&, this](std::error_code /*ec*/, std::size_t /*bytes_sent*/)
            {
                std::cout << "send times : " << count_ << std::endl;
                async_send_data(send_buff, length, ip, port);
                this_thread::sleep_for(chrono::milliseconds(10));
            });
}

void UdpClient::async_send_data(const std::string& ip, const string & port){
    count_++;

    char send_buf[20];
    memset(send_buf, 0, 20);
    StunMessageHeader stun_head_message(BindMethod);
    stun_head_message.encode(send_buf, 20);
    udp::resolver resolver(io_context_);
    remote_endpoints_ = resolver.resolve(udp::v4(), ip, port);

    socket_.async_send_to(
            asio::buffer(send_buf, 20), *remote_endpoints_.begin(),
            [&,this](std::error_code ec, std::size_t bytes_sent)
            {
                this_thread::sleep_for(chrono::seconds (5));
                async_send_data(ip, port);
            });
}

void UdpClient::do_receive(){
        socket_.async_receive_from(
                asio::buffer(msg.data(), 512), recv_endpoint_,
                [&,this](std::error_code ec, std::size_t bytes_recvd)
                {
                    std::cout << "recv " << bytes_recvd 
                    << " from bytes from peer: " << recv_endpoint_.address()
                    << ":" <<recv_endpoint_.port()<< std::endl;


                    if (!ec && bytes_recvd > 0)
                    {
                        std::set<PeerInfoPtr> peers = peer_mgr_.peers();
                        for (const auto &pi : peers) {
                            if (pi->ip() == recv_endpoint_.address().to_string()) {
                                if (pi->ok()) {
                                    break;
                                }
                                for (int i = 0; i < 5; ++i) {
                                    socket_.send_to(asio::buffer(msg.data(), bytes_recvd), recv_endpoint_);
                                }
                                pi->res_port(recv_endpoint_.port());
                            }
                        }
                    }

                    do_receive();
                });
}

uint16_t UdpClient::get_reflexive_port() const{
    return reflexive_port_;
}

std::string UdpClient::get_reflexive_ip() const{
    return reflexive_ip_;
}

int UdpClient::punch(std::string str) {

    char send_buf[20];
    memset(send_buf, 0, 20);
    StunMessageHeader stun_head_message(BindMethod);
    stun_head_message.encode(send_buf, 20);

    std::set<PeerInfoPtr> peers = peer_mgr_.peers();


    for (const auto& pi : peers) {
        if (!pi->ok()) {
            //send_data(punch_msg.data(), punch_msg.length(), pi);
            send_data(send_buf, 20, pi);
        } else {
            cout << "ok to " << pi->ip() << ":" << pi->res_port() << endl;
        }
    }

    return 0;
}
